﻿using System;
using System.Collections;
using System.Linq;
using System.Collections.Generic;
using GeoRef;
using Groupware;
using Search;
using TripLOA.WebService.Serializable;
using TripLOA.WebService.Wrappers;

namespace TripLOA.WebService
{
    /// <summary>
    /// This class defines web-methods for trip operations
    /// </summary>
    public class TripCore : WsCore
    {  
        private static TripCore istance=null;

        #region TripCore Constructors

        /// <summary>
        /// Parameterless Constructor
        /// </summary>
        private TripCore() { }
 
        /// <summary>
        /// Static Constructor.
        /// </summary>
        /// <returns>A new TripCore instance</returns>
        public static TripCore Istance() {
           if(istance==null) {
               istance = new TripCore();
            }
            return istance;
        }

        #endregion

        #region Trip Methods

        /// <summary>
        /// Get a trip by tripId
        /// </summary>
        /// <param name="tripId">Id of the trip to get</param>
        /// <returns>A serializable WSTrip object.</returns>
        public WSTrip getTrip(int tripId)
        {
            // capire come ricevere l'errore x ora sempre ok :-)
            Groupware.Trip gt = null;
            try
            {
                //Temp: Storage NON prende in considerazione la possibilita' di passare un ID inesistente, quindi catturo l'eccezione
                gt = TripManager.GetTripById(tripId);
            }
            catch (Exception) { }

            TripInfo t = null;
            WSTrip wst = null;

            if (gt != null)
            {
                t = new TripInfo(gt);
                wst = new WSTrip("ok", t);
            }
            else
            {
                wst = new WSTrip("fail");
                wst.Message = "Trip not found";
            }

            return wst;
        }

        /// <summary>
        /// Get full info about a given trip.
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="tripId">Id of the trip to search</param>
        /// <returns>A serializable WSFullTrip object</returns>
        public WSFullTrip GetTripDetails(string authToken, int tripId)
        {
            WSFullTrip wst = new WSFullTrip();

            if (!UserCore.Instance.checkAuth(authToken, wst)) return wst;

            // capire come ricevere l'errore x ora sempre ok :-)
            Groupware.Trip gt = null;
            try
            {
                //Temp: Storage NON prende in considerazione la possibilita' di passare un ID inesistente, quindi catturo l'eccezione
                gt = TripManager.GetTripById(tripId);
            }
            catch (Exception) { }

            FullTripInfo t = null;

            if (gt != null)
            {
                t = new FullTripInfo(gt);
                wst = new WSFullTrip("ok", t);
            }
            else
            {
                wst = new WSFullTrip("fail");
                wst.Message = "Trip not found";
            }

            return wst;
        }

        /// <summary>
        /// Book the given trip
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="tripId">Id of the trip to book</param>
        /// <returns>True if the operation is succesful, false otherwise</returns>
        public WSBool BookTrip(string authToken, int tripId)
        {
            WSBool wsb = new WSBool();

            if (!UserCore.Instance.checkAuth(authToken, wsb)) return wsb;

            wsb.Status = "ok";
            wsb.Payload = true;
            return wsb;
        }

        /// <summary>
        /// Cancel the given trip
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="tripId">Id of the trip to cancel</param>
        /// <returns>True if the operation is succesful, false otherwise</returns>
        public WSBool CancelTrip(string authToken, int tripId)
        {
            WSBool wsb = new WSBool();

            if (!UserCore.Instance.checkAuth(authToken, wsb)) return wsb;

            wsb.Status = "ok";
            wsb.Payload = true;
            return wsb;
        }

        /// <summary>
        /// Add a trip to favorites list
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="tripId">Id of the trip to add to favorites</param>
        /// <returns>True if the operation is succesful, false otherwise</returns>
        public WSBool AddTripToFavorites(string authToken, int tripId)
        {
            WSBool wsb = new WSBool();

            if (!UserCore.Instance.checkAuth(authToken, wsb)) return wsb;

            wsb.Status = "ok";
            wsb.Payload = true;
            return wsb;
        }

        /// <summary>
        /// Remove a trip from favorites list.
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="tripId">Id of the trip to remove from favorites</param>
        /// <returns>True if the operation is succesful, false otherwise</returns>
        public WSBool RemoveTripFromFavorites(string authToken, int tripId)
        {
            WSBool wsb = new WSBool();

            if (!UserCore.Instance.checkAuth(authToken, wsb)) return wsb;

            wsb.Status = "ok";
            wsb.Payload = true;
            return wsb;
        }

        /// <summary>
        /// Check if the Trip has been booked by User
        /// TEMP: se l'ID del Trip e` pari restituisce TRUE, altrimenti FALSE
        /// </summary>
        /// <param name="authToken">User authentication token</param>
        /// <param name="tripId">Id of the trip to check</param>
        /// <returns>True if the operation is succesful, false otherwise</returns>
        public WSBool IsBooked(string authToken, int tripId)
        {
            WSBool wsb = new WSBool();

            if (!UserCore.Instance.checkAuth(authToken, wsb)) return wsb;

            wsb.Status = "ok";
            wsb.Payload = true;
            //TEMP
            // tripId pari --> OK
            // tripId dispari --> FAIL
            if (tripId % 2 != 0)
            {
                wsb.Payload = false;
            }
            return wsb;
        }

        /// <summary>
        /// Check if the Trip is in the Favorites List
        /// TEMP: se l'ID del Trip e` pari restituisce TRUE, altrimenti FALSE
        /// </summary>
        /// <param name="authToken">User authentication token</param>
        /// <param name="tripId">Id of the trip to check</param>
        /// <returns>True if the operation is succesful, false otherwise</returns>
        public WSBool IsInFavorites(string authToken, int tripId)
        {
            WSBool wsb = new WSBool();

            if (!UserCore.Instance.checkAuth(authToken, wsb)) return wsb;

            wsb.Status = "ok";
            wsb.Payload = true;
            //TEMP
            // tripId pari --> OK
            // tripId dispari --> FAIL
            if (tripId % 2 != 0)
            {
                wsb.Payload = false;
            }
            return wsb;
        }
        
        /// <summary>
        /// Get the list of the booked trips of the user.
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="start">start search index</param>
        /// <param name="max">stop search index</param>
        /// <returns>A list of Trips serialized in a WSTrip object</returns>
        public WSTrip BookedTrips(string authToken, int start, int max)
        {
            WSTrip wst = new WSTrip();

            if (!UserCore.Instance.checkAuth(authToken, wst)) return wst;

            //TODO


            return wst;
        }

        /// <summary>
        /// Get the list of the trips that user offered
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="start">start search index</param>
        /// <param name="max">stop search index</param>
        /// <returns>A list of trips serialized in a WSTrip object</returns>
        public WSTrip OfferedTrips(string authToken, int start, int max)
        {
            WSTrip wst = new WSTrip();

            if (!UserCore.Instance.checkAuth(authToken, wst)) return wst;
            //Security.TripLoaUser user = new Security.TripLoaUser(username);
            Security.TripLoaUser user = UserCore.Instance.GetUserByAuthToken(authToken);
            var tripList = TripManager.GetAllTripByUserID(user.UserID);
            if (start < 0)
            {
                start = 0;
            }

            if (start > tripList.Count)
            {
                start = tripList.Count;
            }

            if ((start + max) > tripList.Count)
            {
                max = tripList.Count - start;
            }

            wst.Status = "ok";
            tripList = tripList.GetRange(start, max);
            foreach (var t in tripList)
            {
                wst.Payload.Add(new TripInfo(t));
            }
            
            return wst;
        }

        /// <summary>
        /// Get the list of favorite trips of the user
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="start">start search index</param>
        /// <param name="max">stop search index</param>
        /// <returns>A list of trips serialized in a WSTrip object</returns>
        public WSTrip FavoritesTrips(string authToken, int start, int max)
        {
            WSTrip wst = new WSTrip();

            if (!UserCore.Instance.checkAuth(authToken, wst)) return wst;

            //TODO

            return wst;
        }

        [Obsolete]
        public WSInt CreateTrip(
            string authToken, string departureCoords, string arrivalCoords,
            DateTime departureDate, string generalNotes, byte freeSeats)
        {
            WSInt resp = new WSInt();
            
            //Get User from authToken
            if (!UserCore.Instance.Authenticate(authToken))
            {
                resp.Status = "fail";
                resp.Message = "Authentication failed";
                resp.Payload = -1;
                return resp;
            }

            //Get User and UserProfile
            //Security.TripLoaUser user = new Security.TripLoaUser(username);
            Security.TripLoaUser user = UserCore.Instance.GetUserByAuthToken(authToken);
            Security.Profile profile = user.GetProfile();

            //FIXME departure ed arrival sono sottoforma di coordinate, ma serve avere anche l'indirizzo completo da visualizzare!

            //Create a GeoTrip
            GeoTrip gt = GeoRefCore.CreateGeoTrip(departureCoords, arrivalCoords);

            //TODO Let user choose his vehicle...
            VehicleType vType = (VehicleType)profile.UsersVehiclesDetails[6].VehicleDetails.VehicleClass;
            int vId = profile.UsersVehiclesDetails[6].Id; //Select a car type

            for (int i = 0; i < gt.Count; i++)
            {
                gt[i].Vehicle = vType;
                gt[i].VehicleId = vId;

            }
            gt.UpdatePath(); //Update GeoTrip Path, REQUIRED!!

            Groupware.Trip newtrip = new Groupware.Trip(user.UserID, gt);

            newtrip.DepartureDate = departureDate;
            newtrip.Notes = generalNotes;
            newtrip.FreeSeats = freeSeats;
            
            //Il FillStages completa le informazioni ridondanti sulle tratte.
            newtrip.FillStages();
            //Il SaveTrip scrive l'oggetto newtrip nel db
            int newTripId = newtrip.SaveTrip();

            if (newTripId > 0)
            {
                resp.Status = "ok";
            }
            else
            {
                resp.Status = "fail";
                resp.Message = "Error during trip creation. (This method is deprecated, use SaveTrip instead)";
            }
            resp.Payload = newTripId;

            return resp;
        }

        /// <summary>
        /// Saves the trip informations in the database
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="trip">trip to save</param>
        /// <returns>TripId of the saved trip</returns>
        public WSInt SaveTrip(string authToken, FullTripInfo trip)
        {
            WSInt wsi = new WSInt();

            if (!UserCore.Instance.checkAuth(authToken, wsi))
            {
                wsi.Payload = -1;
                return wsi;
            }

            Groupware.Trip gt = trip.generateTrip();
            gt.FillStages();
            int id = gt.SaveTrip();
            //int id = -1;

            if (id < 0)
            {
                var err = gt.Errors.First();
                wsi.Status = "fail";
                //TODO verificare che restituisca qualcosa di comprensibile
                wsi.Message = "Error during trip creation, invalid field: " + err.fieldName;
            }
            else
            {
                wsi.Status = "ok";
            }

            wsi.Payload = id;

            return wsi;
        }

        /// <summary>
        /// Delete a trip from database
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="tripId">Id of the trip to delete</param>
        /// <returns>True if the operation is succesful, false otherwise</returns>
        public WSBool DeleteTrip(string authToken, int tripId)
        {
            WSBool wsb = new WSBool();

            if (!UserCore.Instance.checkAuth(authToken, wsb)) return wsb;

            Security.TripLoaUser user = UserCore.Instance.GetUserByAuthToken(authToken);

            bool res = TripManager.deleteTrip(tripId, user.UserID);

            wsb.Status = "ok";
            wsb.Payload = res;

            return wsb;
        }

        /// <summary>
        /// Search a trip that respects the given parameters
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="fromCoords">Coordinates of departure</param>
        /// <param name="toCoords">Coordinates of arrival</param>
        /// <param name="fromDate">Date from which start search</param>
        /// <param name="toDate">Date in which stop search</param>
        /// <param name="start">start search index</param>
        /// <param name="max">stop search index</param>
        /// <returns>A list of trips serialized in a WSTrip object</returns>
        public WSTrip TripSearch(string authToken, string fromCoords, string toCoords, DateTime fromDate, DateTime toDate, int start, int max)
        {
            Security.TripLoaUser user = UserCore.Instance.GetUserByAuthToken(authToken);
            string username = user.UserName;
            WSTrip wst = new WSTrip();
            //Authentication
            if (!UserCore.Instance.checkAuth(authToken, wst)) return wst;

            GeoLoc fromLoc = null;
            if (fromCoords != null && fromCoords.Length > 0)
            {
                fromLoc = new GeoLoc(fromCoords, null, null);
            }

            GeoLoc toLoc = null;
            if (toCoords != null && toCoords.Length > 0)
            {
                toLoc = new GeoLoc(toCoords, null, null);
            }

            //TEMP Tollerance
            int tollerance = 2000;

            TripParameter departureParameter = TripParameter.Empty; //new TripParameter(fromLoc, tollerance, true);
            if (fromLoc != null)
            {
                departureParameter.Point = fromLoc;
                departureParameter.Radius = tollerance;
                departureParameter.ExactMatch = true;
            }

            TripParameter arrivalParameter = TripParameter.Empty; //new TripParameter(toLoc, tollerance, true);
            if (toLoc != null)
            {
                arrivalParameter.Point = toLoc;
                arrivalParameter.Radius = tollerance;
                arrivalParameter.ExactMatch = true;
                
            }

            TripFieldParameter datePar = TripFieldParameter.Create<DateTime?>("DepartureDate",
                d => d.HasValue && d.Value.Date >= fromDate.Date && d.Value.Date <= toDate);
            TripFieldParameter draftPar = TripFieldParameter.Create<bool>("Draft",d => d == false);
            TripFieldParameter accomplishedPar = TripFieldParameter.Create<bool>("Accomplished", a=> a == false);
            TripFieldParameter ownerPar = TripFieldParameter.Create<int?>("OwnerId", o => o != user.UserID);

            List<TripFieldParameter> tripParList = new List<TripFieldParameter>();
            tripParList.Add(datePar);
            tripParList.Add(draftPar);
            tripParList.Add(accomplishedPar);          
            tripParList.Add(ownerPar);

            List<TripInfo> tripList = new List<TripInfo>();

            var dbList = SearchManager.TripSearch(departureParameter, arrivalParameter, tripParList, null, start, max);
            foreach (var dbTrip in dbList)
            {           
               TripInfo ti = new TripInfo(TripManager.GetTripById(dbTrip.Id));
               tripList.Add(ti);           
            }
            Random r = new Random();
            wst.Status = "ok";
            wst.Payload = tripList;
            wst.CacheID = "CID_" + r.Next(1000, 10000).ToString();
            return wst;
        }

        /// <summary>
        /// Request a reservation for the specified trip
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="tripId">Id of the trip for which request reservation</param>
        /// <param name="fromCoords">Coordinates of departure</param>
        /// <param name="toCoords">Coordinates of arrival</param>
        /// <param name="seatsNo">Number of seats to reserve</param>
        /// <param name="tolerance">Distance tolerance</param>
        /// <param name="notes"></param>
        /// <returns>True if the operation is succesful, false otherwise</returns>
        public WSBool RequestReservation(string authToken, int tripId, string fromCoords, string toCoords, byte seatsNo, int tolerance, string notes)
        {
            WSBool wsb = new WSBool();

            if (!UserCore.Instance.checkAuth(authToken, wsb)) return wsb;
            // By malomo: line added - TripLoaUser is now required for request to join a trip
            Security.TripLoaUser user = UserCore.Instance.GetUserByAuthToken(authToken);

            Groupware.Trip trip = Groupware.TripManager.GetTripById(tripId);

            //Input coords reverse geocoding
            GeoQuery gq = new GeoQuery();

            string[] splittedFromCoords = fromCoords.Split(',');
            string[] splittedToCoords = toCoords.Split(',');

            ///[Changed by Andrea Russino].
            ///
            ///This is only a temporary patch, in fact if the client culture provide a ',' as decimal separator
            ///the splits of lines 532 and 533 produce 4 lengthed arrays, due to the ambiguous usage of char ','
            ///To be robust to this situation a different char shoud be used between lat e long tokens.
            GeoLoc fromLoc = gq.reverseGeoCoding(
                double.Parse(splittedFromCoords[0], System.Globalization.CultureInfo.InvariantCulture), //double.Parse(splittedFromCoords[0].Replace('.', ',')),
                double.Parse(splittedFromCoords[1], System.Globalization.CultureInfo.InvariantCulture) //double.Parse(splittedFromCoords[1].Replace('.', ','))
            );

            GeoLoc toLoc = gq.reverseGeoCoding(
                double.Parse(splittedToCoords[0], System.Globalization.CultureInfo.InvariantCulture), //double.Parse(splittedToCoords[0].Replace('.', ',')),
                double.Parse(splittedToCoords[1], System.Globalization.CultureInfo.InvariantCulture) //double.Parse(splittedToCoords[1].Replace('.', ','))
            );

            try
            {
                // By malomo: added parameter to call - TripLoaUser is now required for request to join a trip
                ReservationRequest req = trip.RequestToJoin(user, seatsNo, fromLoc, toLoc, tolerance, tolerance, notes);
                wsb.Status = "ok";
                wsb.Payload = true;

            }
            catch (Exception ex)
            {
                wsb.Status = "fail";
                wsb.Message = "Error during reservation request: " + ex.Message;
            }

            return wsb;
        }

        #endregion
    }
}
